/*
 * RecordActivity.java
 * 
 * Copyright©2012-2013 JMAS corporation. All rights reserved.
 */
package jp.co.jmas.jmasvideo;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

import jp.co.jmas.jmasvideo.R;
import jp.co.jmas.jmasvideo.dao.Video;

import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.media.MediaRecorder.OnErrorListener;
import android.media.MediaRecorder.OnInfoListener;
import android.os.Build;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.util.Log;
import android.view.Display;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

/**
 * @see http://www.techdoctranslator.com/android/guide/media/camera
 *
 */
public class RecordActivity extends Activity implements SurfaceHolder.Callback, OnErrorListener, OnInfoListener {
	private static final String TAG = "RecordActivity";

	/** SurfaceHolder */
	private SurfaceHolder surfaceHolder;

	/** Camera for Preview */
	private Camera camera;

	/** Camera configured */
	private boolean cameraConfigured;

	/** Camera configured */
	private boolean inPreview;

	/** MediaRecorder */
	private MediaRecorder mediaRecorder;

	/** Controller */
	private Controller controller;

	/** VideoSize */
	private Camera.Size videoSize;

	/** file path of recording video */
	private String filePath;

	/** */
	private long startDate;

	private Timer timer;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// Remove title bar
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

		// Remove notification bar
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
		getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

		setContentView(R.layout.camera);
		SurfaceView surfaceView = (SurfaceView) findViewById(R.id.cameraView);
		surfaceHolder = surfaceView.getHolder();
		surfaceHolder.addCallback(this);

		// Android 3.0以前には必要
		surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

		// コントローラ
		controller = (Controller)findViewById(R.id.controller);

		// シャドウ
		((TextView)findViewById(R.id.textTitleDuration)).setShadowLayer(5f, 2f, 2f, 0xFF000000);
		((TextView)findViewById(R.id.textDuration)).setShadowLayer(5f, 2f, 2f, 0xFF000000);
		((TextView)findViewById(R.id.textTitleSize)).setShadowLayer(5f, 2f, 2f, 0xFF000000);
		((TextView)findViewById(R.id.textSize)).setShadowLayer(5f, 2f, 2f, 0xFF000000);
	}

	@Override
	protected void onResume() {
		super.onResume();
		camera = getCameraInstance();
		startPreview();
	}

	@Override
	public void onPause() {
		if (inPreview) {
			camera.stopPreview();
			inPreview = false;
		}
		releaseMediaRecorder();
		releaseCamera();
		super.onPause();
	}

	private Size initPreview(int width, int height) {
		Size size =null;
		if (camera != null && surfaceHolder.getSurface() != null) {
			try {
				camera.setPreviewDisplay(surfaceHolder);
			} catch (Throwable t) {
				Log.e(TAG, "Exception in setPreviewDisplay()", t);
			}

			if (!cameraConfigured) {
				Camera.Parameters parameters = camera.getParameters();

				// Orientation
				// http://stackoverflow.com/questions/3841122/android-camera-preview-is-sideways
				{
					Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();

					if (display.getRotation() == Surface.ROTATION_0) {
						parameters.setPreviewSize(height, width);
						camera.setDisplayOrientation(90);
					}

					if (display.getRotation() == Surface.ROTATION_90) {
						parameters.setPreviewSize(width, height);
					}

					if (display.getRotation() == Surface.ROTATION_180) {
						parameters.setPreviewSize(height, width);
					}

					if (display.getRotation() == Surface.ROTATION_270) {
						parameters.setPreviewSize(width, height);
						camera.setDisplayOrientation(180);
					}
				}
				size = getBestPreviewSize(width, height, parameters);
				if (size != null) {
					videoSize = size; //getBestVideoSize(parameters);
					parameters.setPreviewSize(size.width, size.height);
					parameters.set("cam_mode", 1);
					camera.setParameters(parameters);
					cameraConfigured = true;
				}
			}
		}
		return size;
	}

	/**
	 * 最適なカメラのプレビューサイズを取得する
	 * 
	 * @param width
	 * @param height
	 * @param parameters
	 * @return
	 */
	private static Camera.Size getBestPreviewSize(int width, int height, Camera.Parameters parameters) {
		// とりあえず0番を入れておいて
		List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
		Camera.Size minSize = sizeList.get(0);
		// 16:9ではなくて4:3で最小のサイズを探す
		for (Camera.Size size : sizeList) {
			Log.v(TAG, String.format("PREVIEW_QUALITY: %d x %d, ratio=%f", size.width, size.height, 1.0 * size.width / size.height));
			if (size.width / (float) size.height > 1.4) {
				// 少なくとも1.4以上は16:9なのでスキップ
				continue;
			}
			if (size.width < minSize.width) {
				// 小さければminSizeに入れる
				minSize = size;
				if (minSize.width <= 320) {
					// 320x240でよし。
					break;
				}
			}
		}

		return minSize;
	}

	private void startPreview() {
		if (cameraConfigured && camera != null) {
			camera.startPreview();
			inPreview = true;
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater menuInflater = getMenuInflater();
		menuInflater.inflate(R.layout.menu, menu);
		return true;
	}


	@Override
	public boolean onOptionsItemSelected(MenuItem item) { 
		Intent intent;
		switch (item.getItemId()) {
 		case R.id.menu_preferences:
			intent = new Intent(this, SettingActivity.class);
			startActivity(intent);
			return true;
 
		default:
			return super.onOptionsItemSelected(item);
		}
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		Log.v(TAG, "surfaceChanged");
		Size cameraSize = initPreview(width, height);

		// TODO SurfaceViewのサイズ変更のアルゴリズムを改善
		// cameraSizeは常にLandscapeとし扱われる?
		SurfaceView view = (SurfaceView)findViewById(R.id.cameraView);
		if (cameraSize != null && view != null) {
			// View size
			android.view.ViewGroup.LayoutParams lp = view.getLayoutParams();

			float ratioScreen   = width / (float) height;
			if (ratioScreen > 1.0) {
				// Landscape
				float ratioCamera = cameraSize.width / (float)cameraSize.height;

				if (ratioScreen > ratioCamera) {
					// TODO SurfaceViewを高さに合わせて、幅を決める。
					lp.width  = width;
					lp.height = height;
				} else {
					// TODO SurfaceViewを幅に合わせて、高さを決める。
					lp.width  = width;
					lp.height = height;
				}
			} else {
				// Portrait
				float ratioCamera = cameraSize.height / (float)cameraSize.width;
				if (ratioScreen > ratioCamera) {
					// TODO SurfaceViewを幅に合わせて、高さを決める。
					lp.width  = width;
					lp.height = height;
				} else {
					// SurfaceViewを高さに合わせて、幅を決める。
					lp.width  = width;
					lp.height = Math.round(width / ratioCamera);
				}
			}

			Log.i("DEBUG", String.format("Screen: %d x %d", width, height));
			Log.i("DEBUG", String.format("Camera: %d x %d", cameraSize.width, cameraSize.height));
			Log.i("DEBUG", String.format("----->: %d x %d", lp.width, lp.height));
			view.setLayoutParams(lp);
		}
		startPreview();
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// do nothing until surfaceChanged
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
	}

	private void releaseMediaRecorder() {
		Log.v(TAG, "releaseMediaRecorder");
		if (mediaRecorder != null) {
			mediaRecorder.reset(); // clear recorder configuration
			mediaRecorder.release(); // release the recorder object
			mediaRecorder = null;
			camera.lock(); // lock camera for later use
		}
	}

	private void releaseCamera() {
		Log.v(TAG, "releaseCamera");
		if (camera != null) {
			camera.release(); // release the camera for other applications
			camera = null;
			cameraConfigured = false;
		}
	}

	private Camera getCameraInstance() {
		Log.d(TAG, "getCameraInstance()");

		Camera c = Camera.open(); // attempt to get a Camera instance

		if (c != null) {
			c.setErrorCallback(null);
			// TODO
		} else {
			Log.e(TAG, "Failed to open camera");
		}
		return c; // returns null if camera is unavailable
	}

	protected boolean startRecording() {
		mediaRecorder = new MediaRecorder();

		 // Step 1: Unlock and set camera to MediaRecorder
		camera.unlock();
		mediaRecorder.setCamera(camera);

		// Step 2: Set sources
		mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
		mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
		// 出力フォーマットにMPEG_4を指定
		mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);

		// 出力ファイルのパスを指定
		File file = new File(AppInfo.getVideoDir(), new SimpleDateFormat("yyyyMMddHHmmss", Locale.US).format(new Date()) + ".mp4");
		filePath = file.getAbsolutePath();
		mediaRecorder.setOutputFile(filePath);

		// フレームレートをセット
		mediaRecorder.setVideoFrameRate(30);
		// 撮影サイズを指定
		mediaRecorder.setVideoSize(videoSize.width, videoSize.height);
		// ビデオエンコーダーにH264をセット
		mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
		// オーディオエンコーダーにAACをセット
		mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
		// プレビュー表示にSurfaceをセット
		mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());

		// 最大録画時間
		int maxDuration = AppInfo.getMaxDuration();
		if (maxDuration > 0) {
			mediaRecorder.setMaxDuration(maxDuration);
		}

		try {
			mediaRecorder.prepare();// 録画準備

		} catch (IllegalStateException e) {
			Log.e(TAG, "start recording", e);
			releaseMediaRecorder();
			return false;
		} catch (IOException e) {
			Log.e(TAG, "start recording" , e);
			releaseMediaRecorder();
			return false;
		}

		try {
			mediaRecorder.setOnInfoListener(this);
			mediaRecorder.start();// 録画開始
			startDate = System.currentTimeMillis();

			timer = new Timer();
			timer.scheduleAtFixedRate(new TimerTask() {
				@Override
				public void run() {
					Log.d("run", "TimerTask Thread id = " + Thread.currentThread().getId());
					runOnUiThread(new Runnable() {
						@Override
						public void run() {
							Button buttonRecord = (Button)findViewById(R.id.buttonRecord);
							TextView textDuration = (TextView)findViewById(R.id.textDuration);
							TextView textSize = (TextView)findViewById(R.id.textSize);
							if (!isFinishing()) {
								long elapse = System.currentTimeMillis() - startDate;
								textDuration.setText(new SimpleDateFormat("mm:ss", Locale.US).format(new Date(elapse)));
								textSize.setText(String.format("%.1fMB", new File(filePath).length() / 1024.0 / 1024.0));

								// ボタン点滅
								if ((elapse / 1000) % 2 == 0) {
									buttonRecord.setBackgroundResource(R.drawable.ic_record_on);
								} else {
									buttonRecord.setBackgroundResource(R.drawable.ic_record_off);
								}
							}
						}
					});
				}
			}, 0, 1000);

		} catch (IllegalStateException e) {
			Log.e(TAG, "start recording", e);
			releaseMediaRecorder();
			return false;
		}
		return true;
	}

	protected void stopRecording() {
		if (timer != null) {
			timer.cancel();
			timer = null;
		}
		// ボタン
		Button buttonRecord = (Button)findViewById(R.id.buttonRecord);
		buttonRecord.setBackgroundResource(R.drawable.ic_record_off);

		if (mediaRecorder != null) {
			try {
				mediaRecorder.stop();
				camera.reconnect();
				releaseMediaRecorder();

				// NamePicker起動
				Intent intent = new Intent(this, NamePickerActivity.class);
				File file = new File(filePath);

				Video video = new Video();
				video.setKey(file.getName().replaceAll("^(.*)\\..*$", "$1"));
				video.setDate(new Date(startDate));
				video.setDuration(System.currentTimeMillis() - startDate);
				video.setSize(file.length());

				// Gingerbread以降の場合、動画ファイルから正確な情報を取得
				updateVideoInfo(video);

				// Thumbnail作成・保存
				Util.createThumbnail(this, filePath, Util.getThumbnailPath(video));

				intent.putExtra(NamePickerActivity.KEY_VIDEO,    video);
				filePath = null;
				startActivity(intent);

			} catch (IllegalStateException e) {
				Log.d(TAG, "IllegalStateException", e);
				// do nothing
			} catch (IOException e) {
				Log.d(TAG, "IOException", e);
				// do nothing
			}
		}
	}

	@SuppressLint("NewApi")
	private void updateVideoInfo(Video video) {
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1) {
			// 情報の補正
			// @see http://stackoverflow.com/questions/8595956/android-how-to-get-a-video-thumbnail-of-a-file-thats-private-to-your-applicati
			MediaMetadataRetriever retriever = new MediaMetadataRetriever();
			try {
				retriever.setDataSource(filePath);
				long duration = Long.parseLong(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
				video.setDuration(duration);

		        Log.d(TAG, "METADATA_KEY_YEAR: " + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
				Log.d(TAG, "METADATA_KEY_DATE: " + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));

			} catch (IllegalArgumentException ex) {
				// do nothing

			} catch (RuntimeException ex) {
				// do nothing

			}
		}
	}

	public void onRecordClicked(View view) {
		if (mediaRecorder == null) {
			// 録画開始
			if (!startRecording()) {
				mediaRecorder = null;
			}
			Log.i(TAG, "Start recording");
		} else {
			Log.i(TAG, "Stop recording");
			stopRecording();
			mediaRecorder = null;
		}
	}

	public void onSettingClicked(View view) {
		Intent intent = new Intent(this, SettingActivity.class);
		startActivity(intent);
	}

	@Override
	public void onError(MediaRecorder mr, int what, int extra) {
		Log.d(TAG, "MediaRecorder error " + Integer.toString(what) + "." + Integer.toString(extra));
		return;
	}

	@Override
	public void onInfo(MediaRecorder mr, int what, int extra) {
		if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
			stopRecording();
		}
	}
}
